package org.bookie.framework.test;

import static org.easymock.EasyMock.expect;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hivemind.Messages;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.TestBase;
import org.apache.tapestry.engine.IEngineService;
import org.apache.tapestry.engine.ILink;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.form.IFormComponent;
import org.apache.tapestry.services.ResponseBuilder;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.valid.IValidationDelegate;
import org.apache.tapestry.valid.ValidationConstraint;
import org.bookie.core.services.IAuthenticationContext;
import org.bookie.framework.Bookie;
import org.bookie.framework.CommonBaseComponent;
import org.bookie.framework.CommonBasePage;
import org.bookie.framework.services.NotificationService;

/**
 * Common class for testing pages.
 * 
 * @author zubairov
 * 
 */
public class PageTestBase extends TestBase {

	/**
	 * Default page name which is assigned to all pages created by the
	 * {@link #newPage(Class, Object[])} method
	 */
	protected final static String DEFAULT_PAGE_NAME = "DEFAULT_PAGE_NAME";

	protected final static String DEFAULT_CLIENT_ID = "componentClientId";
	
	/**
	 * Logger for this class
	 */
	private static final Log log = LogFactory.getLog(PageTestBase.class);

	/**
	 * Delegate mock injected into created page in case it is possible (page
	 * extends {@link CommonBasePage}
	 */
	protected IValidationDelegate delegate;

	/**
	 * Messages mock injected in to created page
	 */
	protected Messages messages;

	/**
	 * Page service mock, injected if page extends (@link CommonBasePage}
	 */
	protected IEngineService pageService;
	
	/**
	 * External service mock, injected if page extends (@link CommonBasePage}
	 */
	protected IEngineService externalService;

	/**
	 * New request cycle
	 */
	protected IRequestCycle cycle;

	protected ResponseBuilder responseBuilder;

	protected NotificationService notificationService;

	protected IAuthenticationContext authenticationContext;

	private IComponentSpecification spec;

	/**
	 * Creates a new instance of the page class
	 * <p>
	 * Following properties of the page is set:
	 * </p>
	 * <li>Page name is set to the {@link #DEFAULT_PAGE_NAME}</li>
	 * <li>Page messages are injected and mock is available via
	 * {@link #messages}</li>
	 * <p>
	 * If page extends {@link CommonBasePage}, then
	 * </p>
	 * <li>Delegate is set and mock is available via {@link #delegate}</li>
	 * 
	 * @param <T>
	 * @param componentClass
	 * @param properties
	 * @return
	 */
	@SuppressWarnings("unchecked")
	protected <T extends IPage> T newPage(java.lang.Class<T> componentClass,
			java.lang.Object... properties) {
		T newInstance = newInstance(componentClass, createParams(componentClass, properties));
		newInstance.setPageName(DEFAULT_PAGE_NAME);
		return newInstance;
	}
	
	protected <T extends IComponent> T newComponent(java.lang.Class<T> componentClass,
			java.lang.Object... properties) {
		T newInstance = newInstance(componentClass, createParams(componentClass, properties));
		newInstance.setClientId(DEFAULT_CLIENT_ID);
		return newInstance;
	}
	
	@SuppressWarnings("unchecked")
	protected Object[] createParams(java.lang.Class componentClass,
			java.lang.Object... properties) {
		cycle = newRequestCycle();
		List<Object> parameters = new ArrayList<Object>();
		parameters.addAll(Arrays.asList(properties));
		messages = newMock(Messages.class);
		addParam(parameters, "messages", messages);
		spec = newMock(IComponentSpecification.class);
		addParam(parameters, "specification", spec);
		if (CommonBasePage.class.isAssignableFrom(componentClass)
				|| CommonBaseComponent.class.isAssignableFrom(componentClass)) {
			delegate = newMock(IValidationDelegate.class);
			addParam(parameters, "delegate", delegate);
			pageService = newMock(IEngineService.class);
			addParam(parameters, "pageService", pageService);
			externalService = newMock(IEngineService.class);
			addParam(parameters, "externalService", externalService);
			responseBuilder = newMock(ResponseBuilder.class);
			addParam(parameters, "responseBuilder", responseBuilder);
			authenticationContext = newMock(IAuthenticationContext.class);
			addParam(parameters, "authenticationContext", authenticationContext);

		} else {
			log.warn("Class " + componentClass + " is not extending "
					+ CommonBasePage.class+" or "+CommonBaseComponent.class);
		}
		if (CommonBaseComponent.class.isAssignableFrom(componentClass)) {
			addParam(parameters, "requestCycle", cycle);
			notificationService = newMock(NotificationService.class);
			addParam(parameters, "notificationService", notificationService);			
		}
		Object[] params = new Object[parameters.size()];
		return parameters.toArray(params);
	}

	/**
	 * Adds a parameter to the parameter list
	 * 
	 * @param parameters
	 * @param parameterName
	 * @param instance
	 */
	private void addParam(List<Object> parameters, String parameterName,
			Object instance) {
		parameters.add(parameterName);
		parameters.add(instance);
	}

	/**
	 * Trains the delegate and message mocks to expect the same behavior as in
	 * the {@link CommonBasePage#recordError}
	 * 
	 * @param errorMessageKey
	 */
	protected void trainRecordError(String errorMessageKey) {
		if (delegate == null) {
			throw new RuntimeException(
					"Delegate is null, please check wherever the tested page extends "
							+ CommonBasePage.class);
		}
		delegate.setFormComponent(null);
		String msg = trainGetMessage(errorMessageKey);
		delegate.record(msg, ValidationConstraint.CONSISTENCY);
	}
	
	/**
	 * Trains the delegate and message mocks to expect the same behavior as in
	 * the {@link CommonBasePage#recordError}
	 * 
	 * @param errorMessageKey
	 */
	protected void trainRecordError(String errorMessageKey, ValidationConstraint constraint, IFormComponent comp) {
		if (delegate == null) {
			throw new RuntimeException(
					"Delegate is null, please check wherever the tested page extends "
							+ CommonBasePage.class);
		}
		delegate.setFormComponent(comp);
		String msg = trainGetMessage(errorMessageKey);
		delegate.record(msg, constraint);
	}

	/**
	 * This method train messages mock to accept a getMessage call
	 * it returns a value which is returned by the getMessage call
	 * 
	 * @param key a key
	 * @return a return value that can be further used
	 */
	protected String trainGetMessage(String key) {
		String msg = "[" + key.toUpperCase() + "]";
		expect(messages.getMessage(key)).andReturn(msg);
		return msg;
	}
	
	/**
	 * This method train messages mock to accept a format call
	 * it returns a value which is returned by the format call
	 * 
	 * @param key a key
	 * @return a return value that can be further used
	 */
	protected String trainFormatMessage(String key, String object) {
		String msg = "[" + key.toUpperCase() + "]-" + object;
		expect(messages.format(key, object)).andReturn(msg);
		return msg;
	}
	
	/**
	 * This method train messages mock to accept a format call
	 * it returns a value which is returned by the format call
	 * 
	 * @param key a key
	 * @return a return value that can be further used
	 */
	protected String trainFormatMessage(String key, String arg1, String arg2) {
		String msg = "[" + key.toUpperCase() + "]-" + arg1 + "-"+arg2;
		expect(messages.format(key, arg1, arg2)).andReturn(msg);
		return msg;
	}
	
	/**
	 * Trains page service to return a ILink for the page with given name
	 * 
	 * @param pageName
	 * @return
	 */
	protected ILink trainGetLink(String pageName) {
		ILink newLink = newMock(ILink.class);
		expect(pageService.getLink(false, pageName)).andReturn(
				newLink);
		return newLink;
	}
	
	/**
	 * Creates a <code>PageEvent</code> for a <code>IPage</code>.
	 * @param page page to create <code>PageEvent</code> for.
	 */
	protected PageEvent createPageEvent(IPage page){
		return new PageEvent(page, page.getRequestCycle());
	}
	
	protected void trainAjaxUpdateMethod(IComponent component, String... id) {
		trainAjaxUpdateMethod(component.getPage(), id);
	}
	
	protected void trainAjaxUpdateMethod(IPage page, String... ids) {
		for(int i=0; i<ids.length; i++){
			responseBuilder.updateComponent(ids[i]);
		}
	}
	
	protected String trainFormatError(String key, String argument1){
		delegate.setFormComponent(null);
		String msg = "[" + key.toUpperCase() + "]";
		expect(messages.format(key, argument1)).andReturn(msg);
		delegate.record(msg, ValidationConstraint.CONSISTENCY);
		return msg;
	}
	
	protected String trainActivateExternalMailPage(Object[] params, boolean fail){
		List names = new ArrayList();
		for(Object next:params){
			names.add(next);
		}
		if(fail){
			names.add(System.currentTimeMillis());
		}
		expect(spec.getPropertySpecificationNames()).andReturn(names);
		if(fail){
			return trainFormatMessage("page-parameters-missing", DEFAULT_PAGE_NAME, Bookie.listToString(names)); 
		}
		return null;
	}
}
